前兩天辛苦的切版,今天要換換口味,先來做個路由。對網頁有概念的人會知道,網址就是對應 HTML 檔案資料夾的位置,在 react 裡面想要做出網站結構(也就是網址),要利用 react-router-dom,別忘了將 pages 中的資料夾結構與 app.tsx 的路由內容對應起來,之後也會比較好回憶當初的思路。
這邊有個小小的提醒,現在最新的版本是 react-router-dom v6,別安裝到舊版囉!
一樣都用自己習慣的套件管理軟體來安裝,以下是官方文件裡面的範例:
yarn add react-router-dom
繼續看官方文件,可以看到以下的示範:
import { render } from "react-dom";
import { BrowserRouter, Routes, Route } from "react-router-dom";
// 引入 react-router-dom 需要的元件
render(
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<Home />} />
<Route path="teams" element={<Teams />}>
<Route path=":teamId" element={<Team />} />
<Route path="new" element={<NewTeamForm />} />
<Route index element={<LeagueStandings />} />
</Route>
</Route>
</Routes>
</BrowserRouter>,
document.getElementById("root")
);
一步一步模仿官方文件的做法,在自己的專案裡面使用 react-router-dom,<BrowserRouter>
可以跟之前安裝 <ChakraProvider>
一樣放在 index.tsx :
import React from "react";
import ReactDOM from "react-dom/client";
import { ChakraProvider } from "@chakra-ui/react";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import "./index.css";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<BrowserRouter>
<ChakraProvider>
<App />
</ChakraProvider>
</BrowserRouter>
</React.StrictMode>
);
從頁面上的 menu 可以看出,我們的網站有兩個功能模組,一個是 Todos,另一個是 Dashboard,所以 menu 右邊的兩個按鈕,就是進入這兩個模組的入口,網址分別是 /todo 與 /dashboard,試著把這兩個網址裝到 App.tsx :
import React from "react";
import DefaultLayout from "./layouts/DefaultLayout";
import TodoPage from "./pages/TodoPage";
import { Routes, Route } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/todo" element={<TodoPage />}></Route>
<Route path="/dashboard" element={<>dashboard.</>}></Route>
</Routes>
);
}
export default App;
接下來把專案 run 起來,在網址列輸入 /todo 與 /dashboard,可以看到對應的頁面,那就正確了。
不過有點奇怪啊,menu 不見了,每個頁面都應該看見它吧,這時候我們來建立路由外框,用外框把內容框住,作法如下 :
新增資料夾 Layout
在資料夾中新增檔案 DefaultLayout.tsx,從 TodoPage.tsx 那裏把全站共用的部分加進去 :
import React from "react";
import { Outlet, Link } from "react-router-dom";
const DefaultLayout: React.FC = () => {
return (
<section>
<div className="flex justify-between items-center shadow px-[20px] py-[20px]">
<h1 className="text-[30px] font-medium">MyNote</h1>
<div>
<button className="px-[8px] py-[2px] border rounded mr-[8px]">
Dashboard
</button>
<button className="px-[8px] py-[2px] border rounded">Todos</button>
</div>
</div>
</section>
);
};
export default DefaultLayout;
<Outlet />
,記得要引入 :import React from "react";
import { Outlet, Link } from "react-router-dom";
const DefaultLayout: React.FC = () => {
return (
<section>
<div className="flex justify-between items-center shadow px-[20px] py-[20px]">
<h1 className="text-[30px] font-medium">MyNote</h1>
<div>
<button className="px-[8px] py-[2px] border rounded mr-[8px]">
Dashboard
</button>
<button className="px-[8px] py-[2px] border rounded">Todos</button>
</div>
</div>
<Outlet />
</section>
);
};
export default DefaultLayout;
function App() {
return (
<Routes>
<Route path="/" element={<DefaultLayout />}>
<Route index element={<>home.</>}></Route>
<Route path="/todo" element={<TodoPage />}></Route>
<Route path="/dashboard" element={<>dashboard.</>}></Route>
</Route>
</Routes>
);
}
export default App;
<Link>
來連結頁面接下來在 run 起來的網頁,看看成果。
每一隻檔案最後呈現的樣子。
DefaultLayout.tsx
import React from "react";
import { Outlet, Link } from "react-router-dom";
const DefaultLayout: React.FC = () => {
return (
<section>
<div className="flex justify-between items-center shadow px-[20px] py-[20px]">
<h1 className="text-[30px] font-medium">MyNote</h1>
<div>
<button className="px-[8px] py-[2px] border rounded mr-[8px]">
<Link to="/dashboard">Dashboard</Link>
</button>
<button className="px-[8px] py-[2px] border rounded">
<Link to="/todo">Todos</Link>
</button>
</div>
</div>
<Outlet />
</section>
);
};
export default DefaultLayout;
App.tsx
import React from "react";
import DefaultLayout from "./layouts/DefaultLayout";
import TodoPage from "./pages/TodoPage";
import { Routes, Route } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/" element={<DefaultLayout />}>
<Route index element={<>home.</>}></Route>
<Route path="/todo" element={<TodoPage />}></Route>
<Route path="/dashboard" element={<>dashboard.</>}></Route>
</Route>
</Routes>
);
}
export default App;
然後,別忘了要改 TodoPage.tsx 的內容喔 :
import React from "react";
import {
Table,
Thead,
Tbody,
Tfoot,
Tr,
Th,
Td,
TableCaption,
TableContainer,
} from "@chakra-ui/react";
import {
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
} from "@chakra-ui/react";
import { Checkbox, CheckboxGroup } from "@chakra-ui/react";
const TodoPage: React.FC = () => {
return (
<>
<div className="pt-[20px] max-w-[1280px] mx-auto my-0">
<div className="flex justify-end items-center">
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="32" height="32" rx="6" fill="#EDF2F7" />
<path
d="M23.7918 16.5416H17.5418V22.7916H15.4585V16.5416H9.2085V14.4583H15.4585V8.20831H17.5418V14.4583H23.7918V16.5416Z"
fill="black"
/>
</svg>
<span className="ml-[8px]">新增待辦</span>
</div>
</div>
<div className="flex justify-between py-[20px] max-w-[1280px] mx-auto my-0">
<div className="flex items-center">
<span>Show rows per page</span>
<NumberInput className="w-[80px] ml-[8px]" min={1} max={10} size="sm">
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</div>
<div className="flex items-center">
<span>1-8 of 32</span>
<div className="flex">
<span>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g opacity="0.5">
<path
d="M15.2188 16L18.5188 19.3L17.5762 20.2427L13.3335 16L17.5762 11.7573L18.5188 12.7L15.2188 16Z"
fill="#2D3748"
/>
</g>
</svg>
</span>
<span>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M16.7814 16L13.4814 12.7L14.4241 11.7573L18.6668 16L14.4241 20.2427L13.4814 19.3L16.7814 16Z"
fill="#2D3748"
/>
</svg>
</span>
</div>
</div>
</div>
<div className="max-w-[1280px] mx-auto my-0">
<TableContainer className="m-2">
<Table variant="striped">
<Thead>
<Tr>
<Th>
<Checkbox />
</Th>
<Th>任務標題</Th>
<Th>詳情</Th>
<Th>操作</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>
<Checkbox />
</Td>
<Td>購物</Td>
<Td>日用品</Td>
<Td className="flex items-center">
<span>
<svg
width="20"
height="20"
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.75 21.825V25.625C3.75 25.975 4.025 26.25 4.375 26.25H8.175C8.3375 26.25 8.5 26.1875 8.6125 26.0625L22.2625 12.425L17.575 7.73748L3.9375 21.375C3.8125 21.5 3.75 21.65 3.75 21.825V21.825ZM25.8875 8.79998C26.375 8.31248 26.375 7.52498 25.8875 7.03748L22.9625 4.11248C22.475 3.62498 21.6875 3.62498 21.2 4.11248L18.9125 6.39998L23.6 11.0875L25.8875 8.79998Z"
fill="black"
/>
</svg>
</span>
<span>
<svg
width="25"
height="25"
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.39946 24.6985C8.39946 26.0848 9.53373 27.2191 10.9201 27.2191H21.0024C22.3888 27.2191 23.523 26.0848 23.523 24.6985V9.57494H8.39946V24.6985ZM11.4998 15.7252L13.2768 13.9482L15.9612 16.62L18.6331 13.9482L20.4101 15.7252L17.7383 18.397L20.4101 21.0689L18.6331 22.8459L15.9612 20.174L13.2894 22.8459L11.5124 21.0689L14.1842 18.397L11.4998 15.7252ZM20.3723 5.79404L19.112 4.53374H12.8105L11.5502 5.79404H7.13916V8.31464H24.7833V5.79404H20.3723Z"
fill="black"
/>
</svg>
</span>
</Td>
</Tr>
<Tr>
<Td>
<Checkbox />
</Td>
<Td>鐵人賽</Td>
<Td>day-10</Td>
<Td className="flex items-center">
<span>
<svg
width="20"
height="20"
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.75 21.825V25.625C3.75 25.975 4.025 26.25 4.375 26.25H8.175C8.3375 26.25 8.5 26.1875 8.6125 26.0625L22.2625 12.425L17.575 7.73748L3.9375 21.375C3.8125 21.5 3.75 21.65 3.75 21.825V21.825ZM25.8875 8.79998C26.375 8.31248 26.375 7.52498 25.8875 7.03748L22.9625 4.11248C22.475 3.62498 21.6875 3.62498 21.2 4.11248L18.9125 6.39998L23.6 11.0875L25.8875 8.79998Z"
fill="black"
/>
</svg>
</span>
<span>
<svg
width="25"
height="25"
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.39946 24.6985C8.39946 26.0848 9.53373 27.2191 10.9201 27.2191H21.0024C22.3888 27.2191 23.523 26.0848 23.523 24.6985V9.57494H8.39946V24.6985ZM11.4998 15.7252L13.2768 13.9482L15.9612 16.62L18.6331 13.9482L20.4101 15.7252L17.7383 18.397L20.4101 21.0689L18.6331 22.8459L15.9612 20.174L13.2894 22.8459L11.5124 21.0689L14.1842 18.397L11.4998 15.7252ZM20.3723 5.79404L19.112 4.53374H12.8105L11.5502 5.79404H7.13916V8.31464H24.7833V5.79404H20.3723Z"
fill="black"
/>
</svg>
</span>
</Td>
</Tr>
<Tr>
<Td>
<Checkbox />
</Td>
<Td>折棉被</Td>
<Td></Td>
<Td className="flex items-center">
<span className="cursor-pointer">
<svg
width="20"
height="20"
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.75 21.825V25.625C3.75 25.975 4.025 26.25 4.375 26.25H8.175C8.3375 26.25 8.5 26.1875 8.6125 26.0625L22.2625 12.425L17.575 7.73748L3.9375 21.375C3.8125 21.5 3.75 21.65 3.75 21.825V21.825ZM25.8875 8.79998C26.375 8.31248 26.375 7.52498 25.8875 7.03748L22.9625 4.11248C22.475 3.62498 21.6875 3.62498 21.2 4.11248L18.9125 6.39998L23.6 11.0875L25.8875 8.79998Z"
fill="black"
/>
</svg>
</span>
<span className="cursor-pointer">
<svg
width="25"
height="25"
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.39946 24.6985C8.39946 26.0848 9.53373 27.2191 10.9201 27.2191H21.0024C22.3888 27.2191 23.523 26.0848 23.523 24.6985V9.57494H8.39946V24.6985ZM11.4998 15.7252L13.2768 13.9482L15.9612 16.62L18.6331 13.9482L20.4101 15.7252L17.7383 18.397L20.4101 21.0689L18.6331 22.8459L15.9612 20.174L13.2894 22.8459L11.5124 21.0689L14.1842 18.397L11.4998 15.7252ZM20.3723 5.79404L19.112 4.53374H12.8105L11.5502 5.79404H7.13916V8.31464H24.7833V5.79404H20.3723Z"
fill="black"
/>
</svg>
</span>
</Td>
</Tr>
</Tbody>
</Table>
</TableContainer>
</div>
</>
);
};
export default TodoPage;
今天的進度是在自己的專案裡面模仿 react-router-dom 官方文件範例,完成了 menu 導航,做出了 tab 切換的效果,初步了解在 react 建立路由的方式。